package wiEngine;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import wiBase.WiException;
import wiBase.WiObject;
import wiBase.WiScheduler;
import wiBase.baseInterface.OnDrawerListener;
import wiBase.baseInterface.OnEngineListener;
import wiBase.baseInterface.OnOutputStreamListener;
import wiBase.baseObject.Event;
import wiBase.baseObject.Node;
import wiBase.baseObject.Packet;
import wiBase.baseObject.WiNetwork;
import wiModel.WiModelAdapter;

/**
 * WiEngine control all action of system. You can set/add Listener for actions.
 */
public class WiEngine extends WiScheduler implements OnEngineListener {
    
    
    
    private boolean DEBUG = false;
    private boolean useNative = false;
    private boolean debugNative = true;
    private boolean isPlay1 = false;
    private boolean isStop1 = true;
    private static WiEngine mEngine = null;
    private WiModelAdapter mAdapter = null;
    private ArrayList<WiNetwork> mNetworks = null;
//    private static CounterCurrentTime COUNTER_CURRENT_TIME;
    ArrayList<OnDrawerListener> mLstDrawListener = null;
    ScheduledFuture future;
    
    boolean flag_start_from_pause = false;

    public WiEngine() {
	initComponent();
    }

    /**
     * Engine use singleton mechanism: 1 app use for 1 Engine. console and graph
     * can update onTime.
     */
    public static WiEngine getInstance() {
	if (mEngine == null) {
	    mEngine = new WiEngine();
	}
	return mEngine;
    }

    /**
     * reset current value of Engine. get WiEngine object from getInstance()
     * method
     */
    public void clearInstance() {
	mEngine = new WiEngine();
    }

    private void initComponent() {
	mAdapter = new WiModelAdapter();
	mNetworks = new ArrayList<>();
	mScheduler = new WiSchedulerThreadPoolExecutor(100);
	_engineControlListener = new ArrayList<>();
    }

    // Adapter APIs
    @Override
    public void setTraceFile(String traceFile) {
	mAdapter.setTraceFile(traceFile);

	// callback
	for (OnEngineControlListener listener : _engineControlListener) {
	    listener.onTraceFileChange(traceFile);
	}
    }

    /**
     * @return Model Adapter
     *
     */
    public WiModelAdapter getAdapter() {
	return mAdapter;
    }

    /**
     * @return WiNetwork object at time (second)
     */
    public WiNetwork getNetwork(long time) {
	for (int i = 0; i < mNetworks.get(mNetworks.size() - 1).get_time(); ++i) {
	    if (mNetworks.get(i).get_time() < time && mNetworks.get(i + 1).get_time() > time) {
		return mNetworks.get(i);
	    }
	}
	return null;
    }

    /**
     * @return sub-network not implement
     */
    public ArrayList<Integer> getSubNetwork(long time, ArrayList<Integer> idNodes) {
	return null;
    }

    /**
     * @param idNode: index of a node
     * @return node have id=idNode
     */
    @Override
    public Node getNode(int idNode) {
	return mAdapter.getNodeById(idNode);
    }

    @Override
    public Packet getPacket(int idPacket) {
	return mAdapter.getPacketById(idPacket);
    }

    /**
     * @param idEvent: index of a event
     * @return event have id=idEvent
     */
    public Event getEvent(int idEvent) {
	return mAdapter.getEventById(idEvent);
    }

    /**
     * @param time: nanosecond
     * @return event at time. null for not found event
     */
    public Event getEvent(long time) {
	for (int i = 0; i < mAdapter.getListEvents().size(); ++i) {
	    Event e = (Event) mAdapter.getListEvents().get(i);
	    if (e.getTime() > time) {
		return (Event) mAdapter.getListEvents().get(i - 1);
	    }
	}
	return null;
    }

    public ArrayList<Node> getNodes(Integer... idNodes) {
	ArrayList<Node> _result = new ArrayList<>();
	for (WiObject _node : mAdapter.getListNodes()) {
	    for (int _idNode : idNodes) {
		if (((Node) _node).id == _idNode) {
		    _result.add((Node) _node);
		}
	    }
	}
	return _result;
    }

    @Override
    public java.util.ArrayList<WiObject> getListNodes() {
	return mAdapter.getListNodes();
    }

    @Override
    public java.util.ArrayList<WiObject> getListPackets() {
	return mAdapter.getListPackets();
    }

    /**
     * @return list of Nodes or Events relate to the packet.
     *
     */
    @Override
    public ArrayList<Event> tracePacket(int idPacket) {
	ArrayList<Event> _result = new ArrayList<>();

	for (WiObject _event : mAdapter.getListEvents()) {
	    if (((Event) _event).packetId.equals(idPacket + "")) {
		_result.add((Event) _event);
	    }
	}

	return _result;
    }

    public void setNodeSleep(int idNode) {
	mAdapter.getNodeById(idNode).setSleep(true);
    }

    public void setNodeWakeUp(int idNode) {
	mAdapter.getNodeById(idNode).setSleep(false);
    }
    /*
     * @param startTime: nanosecond.
     * @param endTime: nanosecond.
     * 
     */

    protected void startSimulation(long startTime, long endTime) throws InterruptedException {

	mScheduler = new WiSchedulerThreadPoolExecutor();

	if (mLstDrawListener != null) {
	    // prepare
	    for (OnDrawerListener itemListener : mLstDrawListener) {
		// sleep/wake/move
		for (WiNetwork itemNetwork : mNetworks) {
		    if (itemNetwork.get_time() < endTime) {
			itemListener.updateState(itemNetwork);
		    }
		}
		// packet flying
//		for (Event itemEvent : getListAllEvents(startTime - BEFORE_TIME, START_TIME)) {
//		    itemListener.updateState(itemEvent);
//		}
	    }
	}
	Event endEvent = null;
	// schedule
	EventTraceListener eventListener = new EventTraceListener(this, mLstDrawListener);
	ArrayList<Event> lst = getListAllEvents(startTime, endTime, true);
	System.out.println(startTime + "----" + endTime + "---size: " + lst.size());
	long runTime;
	for (WiObject event : lst) {
	    endEvent = (Event) event;
	    runTime = (long) (((Event) event).getTime() - (double) startTime);
	    runTime = (long) ((double) runTime * CURRENT_SPEED);

	    future = mScheduler.schedule((Event) event, runTime, CURRENT_TIME_UNIT);
	    ((Event) event).setTraceSchedulerListener(eventListener);
	}
	if (endEvent == null) {
	    return;
	}
	endEvent.type = "end";
	runTime = (long) (((Event) endEvent).getTime() + 1 - (double) startTime);
	runTime = (long) ((double) runTime * CURRENT_SPEED);
	future = mScheduler.schedule((Event) endEvent, runTime, CURRENT_TIME_UNIT);
	((Event) endEvent).setTraceSchedulerListener(eventListener);
    }

    protected void startSimulation(long startTime, long endTime, boolean isImplicit) throws InterruptedException {

	mScheduler = new WiSchedulerThreadPoolExecutor(0);
	if (mLstDrawListener != null) {
	    // prepare
	    for (OnDrawerListener itemListener : mLstDrawListener) {
		for (WiNetwork itemNetwork : mNetworks) {
		    if (itemNetwork.get_time() < endTime) {
			itemListener.updateState(itemNetwork);
		    }
		}
		for (Event itemEvent : getListAllEvents(startTime - BEFORE_TIME, START_TIME)) {
		    itemListener.updateState(itemEvent);
		}
	    }
	}

	// schedule
	for (WiObject event : getListAllEvents(startTime, endTime)) {
	    long runTime = (long) (((Event) event).getTime() - (double) startTime);
	    runTime = (long) ((double) runTime * CURRENT_SPEED);
	    ((Event) event).setTraceSchedulerListener(new EventTraceListener(this, mLstDrawListener));
	    if (!isImplicit) {
		future = mScheduler.schedule((Event) event, runTime, CURRENT_TIME_UNIT);
	    } else {
		((Event) event).run();
	    }
	}
    }

    protected void startSimulationFromWiNetwork(long startTime, long endTime, WiNetwork network) throws InterruptedException {
	mScheduler = new WiSchedulerThreadPoolExecutor(0);
	if (mLstDrawListener != null) {
	    // prepare
	    for (OnDrawerListener itemListener : mLstDrawListener) {
		for (WiNetwork itemNetwork : mNetworks) {
		    if (itemNetwork.get_time() < endTime) {
			itemListener.updateState(itemNetwork);
		    }
		}
		for (Event itemEvent : getListAllEvents(startTime - BEFORE_TIME /*1 second*/, START_TIME)) {
		    itemListener.updateState(itemEvent);
		}
	    }
	}

	// schedule
	for (WiObject event : network.getLstEvents()) {
	    long runTime = (long) (((Event) event).getTime() - (double) startTime);
	    runTime = (long) ((double) runTime * CURRENT_SPEED);
	    future = mScheduler.schedule((Event) event, runTime, CURRENT_TIME_UNIT);
	    ((Event) event).setTraceSchedulerListener(new EventTraceListener(this, mLstDrawListener));
	}
    }

    protected void startSimulationFromGroupNode(ArrayList<Node> lstNode) throws InterruptedException {
	if (lstNode.isEmpty()) {
	    if (DEBUG) {
		System.out.print("no node to play");
	    }
	    return;
	} else if (lstNode.size() == mAdapter.getListNodes().size()) {
	    startSimulation(-1, -1);
	    return;
	}
	long minTime = Long.MAX_VALUE;
	long maxTime = Long.MIN_VALUE;
	for (Node node : lstNode) {
	    for (WiObject event : hashNode.get(node.id + "")) {
		if (((Event) event).getTime() < minTime) {
		    minTime = ((Event) event).getTime();
		}
		if (((Event) event).getTime() > maxTime) {
		    maxTime = ((Event) event).getTime();
		}
	    }
	}
	startSimulation(minTime, maxTime);
    }

    /**
     * add a node to trace. if node is traced, OutputListener of the node be not
     * equal NULL. so Schedule will call the listener of the node.
     */
    public void addTraceNode(int idNode, OnOutputStreamListener listener) throws WiException {
	for (WiObject _node_ : mAdapter.getListNodes()) {
	    if (((Node) _node_).id == idNode) {
		_node_.setOutputListener(listener);
		break;
	    }
	}
    }

    /**
     * add nodes to trace.
     *
     * @see addTraceNode()
     */
    public void addTraceNodes(ArrayList<Integer> arrayId, OnOutputStreamListener listener) throws WiException {
	for (WiObject _object : getListNodes()) {
	    for (int id : arrayId) {
		if (id == ((Node) _object).id) {
		    ((Node) _object).setOutputListener(listener);
		}
	    }
	}
    }

    protected WiNetwork setNodesToTrace(ArrayList<Integer> lstIdNodes, OnOutputStreamListener listener) throws WiException {
	WiNetwork netWorkToTrace = new WiNetwork(-1);

	ArrayList<WiObject> lstNodes = this.getListNodes(lstIdNodes);

	netWorkToTrace.setLstNodes(lstNodes);
	boolean isStart = true;
	for (WiObject object : this.getListAllEvents(-1, -1)) {
	    ArrayList<Integer> lstCompare = new ArrayList<>();
	    Packet packet = null;
	    Event event = (Event) object;
	    switch (event.type) {
		case "send":
		case "forward":
		case "receive":
		    lstCompare.add(Integer.parseInt(event.destId));
		    lstCompare.add(Integer.parseInt(event.sourceId));

		    int idPacket = Integer.parseInt(event.packetId);
		    packet = this.getPacket(idPacket);
		    lstCompare.add(Integer.parseInt(packet.destId));
		    lstCompare.add(Integer.parseInt(packet.sourceId));
		    break;
		case "drop":
		    idPacket = Integer.parseInt(event.packetId);
		    packet = this.getPacket(idPacket);
		    lstCompare.add(Integer.parseInt(packet.destId));
		    lstCompare.add(Integer.parseInt(packet.sourceId));
		case "move":
		case "sleep":
		case "wake":
		    lstCompare.add(Integer.parseInt(event.nodeId));
		    break;
	    }

	    for (Integer idNode : lstIdNodes) {
		if (lstCompare.contains(idNode)) {
		    if (isStart) {
			netWorkToTrace.setTime(event.getTime());
			isStart = false;
		    }
		    netWorkToTrace.getLstEvents().add(event);
		    if (packet != null) {
			netWorkToTrace.getLstPackets().add(packet);
		    }
		    break;
		}
	    }

	    if (object.equals(this.getListAllEvents(-1, -1).get(this.getListAllEvents(-1, -1).size() - 1))) {
		netWorkToTrace.setEndTime(event.getTime());
	    }
	}
	return netWorkToTrace;
    }

    public void runFromWiNetwork(WiNetwork network) throws InterruptedException {
	startSimulationFromWiNetwork(network.get_time(), network.getEndTime(), network);
    }

    /**
     * @see addTraceNode
     */
    public void addTracePacket(int idPacket, OnOutputStreamListener listener) throws WiException {
	for (WiObject _packet_ : mAdapter.getListPackets()) {
	    if (((Packet) _packet_).id.equals(idPacket + "")) {
		_packet_.setOutputListener(listener);
		break;
	    }
	}
    }

    /**
     * @see addTraceNodes
     */
    public void addTracePackets(ArrayList<String> arrayId, OnOutputStreamListener listener) throws WiException {
	for (WiObject _object : mAdapter.getListPackets()) {
	    for (String id : arrayId) {
		if (id.equals(((Packet) _object).id)) {
		    ((Packet) _object).setOutputListener(listener);
		}
	    }
	}
    }

    /**
     * @see addTraceNode
     */
    public void addTraceEvent(String type, OnOutputStreamListener listener) {
	if (type.equals("broastcast")) {
	    for (WiObject _event_ : mAdapter.getListEvents()) {
		Event event = (Event) _event_;
		try {
		    if (event.sourceId.equals("-1") || event.destId.equals("-1")) {
			_event_.setOutputListener(listener);
		    }
		} catch (NullPointerException e) {
		}
	    }
	} else if (type.equals("ghost")) {
	} else {
	    for (WiObject _event_ : mAdapter.getListEvents()) {
		Event event = (Event) _event_;
		try {
		    if (event.sourceId.equals("-1") || event.destId.equals("-1")) {
			continue;
		    }
		} catch (NullPointerException e) {
		}
		if (((Event) _event_).type.equals(type + "")) {
		    _event_.setOutputListener(listener);
		}
	    }
	}

    }

    /**
     * start simulation from 0 to finish use ThreadPoolExecutor to schedule
     * events.
     *
     * @see run(long startTime, long endTime);
     */
    @Override
    public void run() {
	System.out.println("Start Time:" + START_TIME + "-" + FINISH_TIME);
	// callback
	for (OnEngineControlListener listener : _engineControlListener) {
	    listener.onRun(START_TIME, FINISH_TIME);
	}



	run(START_TIME, FINISH_TIME);


    }

    /**
     * start simulation from startTime to endTime use ThreadPoolExecutor to
     * schedule events.
     *
     * @see startSimulation(long startTime, long endTime);
     * @param startTime : second
     */
    @Override
    public void run(long startTime, long endTime) {
	STATE_CURRENT=STATE_PLAY;
	// callback
	for (OnEngineControlListener listener : _engineControlListener) {
	    listener.onRun(startTime, endTime);
	}

	long startTmp = -1L;
	long endTmp = -1L;

	if (startTime >= 0) {
	    startTmp = (long) (Double.parseDouble(startTime + "") * 1E9);
	} else {
	    startTime = 0;
	}
	if (endTime >= 0) {
	    endTmp = (long) (Double.parseDouble(endTime + "") * 1E9);
	} else {
	    endTmp = (long) (getAdapter().getDurationSimulation() * 1E9);
	}
	try {
	    if (!flag_start_from_pause) {
		setStartTime9E(startTmp);
		setFinishTime9E(endTmp);
		flag_start_from_pause = false;
	    }

	    setCurrentTime9E(startTmp);
	    for (OnEngineControlListener listener : _engineControlListener) {
		listener.onUpdateCurrentTime(getStartTime9E(), getFinishTime9E(), getCurrentTime9E());
	    }
//	    try {
//		if (COUNTER_CURRENT_TIME.isAlive()) {
//		    COUNTER_CURRENT_TIME.interrupt();
//		}
//	    } catch (NullPointerException e) {
//	    }
//	    COUNTER_CURRENT_TIME = new CounterCurrentTime();
//	    COUNTER_CURRENT_TIME.start();
	    startSimulation(startTmp, endTmp);
	} catch (InterruptedException e) {
	    e.printStackTrace();
	}
    }

    @Override
    public void addOnDrawerListener(OnDrawerListener listener) {
	if (this.mLstDrawListener == null) {
	    this.mLstDrawListener = new ArrayList<>();
	}
	this.mLstDrawListener.add(listener);
    }
    /*
     * @param startTime: (long)nanosecond.
     */

    @Override
    public ArrayList<Event> getListAllEvents(long startTime, long endTime) {
	ArrayList<Event> result = new ArrayList<>();
	long startTmp = -1L;
	long endTmp = Long.MAX_VALUE;

	if (startTime >= 0) {
	    startTmp = startTime;
	} else {
	    startTmp = 0;
	}
	if (endTime >= 0) {
	    endTmp = endTime;
	} else {
	    endTmp = Long.MAX_VALUE;
	}
	for (WiObject object : mAdapter.getListEvents()) {
	    Event event = (Event) object;
	    if (event.getTime() > startTmp && event.getTime() < endTmp) {
		result.add(event);
	    }
	}
	//System.out.println(result.size());
	return result;
    }

    public ArrayList<Event> getListAllEvents(long startTime9E, long endTime9E, boolean isGhost) {
	if (!isGhost) {
	    return getListAllEvents(startTime9E, endTime9E);
	}
	ArrayList<Event> result = new ArrayList<>();
	long startTmp9E = -1L;
	long endTmp9E = Long.MAX_VALUE;

	if (startTime9E >= 0) {
	    startTmp9E = startTime9E;
	} else {
	    startTmp9E = 0;
	}
	if (endTime9E >= 0) {
	    endTmp9E = endTime9E;
	} else {
	    endTmp9E = Long.MAX_VALUE;
	}
	System.out.println("size event:" + mAdapter.getListEvents().size());
	System.out.println("size ghost:" + lstGhostEvent.size());
	ArrayList<Event> result1 = new ArrayList<>();
	for (WiObject object : mAdapter.getListEvents()) {
	    Event event = (Event) object;

	    if (event.getTime() >= startTmp9E && event.getTime() <= endTmp9E) {
		result1.add(event);
	    }
	}
	ArrayList<Event> result2 = new ArrayList<>();
	int lstIndex = 0;
	for (int i = 0; i < result1.size() - 1; ++i) {
	    Event e1 = result1.get(i);
	    Event e2 = result1.get(i + 1);
	    result2.add(e1);
	    if (e2 != null) {
		for (int j = lstIndex; j < lstGhostEvent.size(); ++j) {
		    Event ghostEvent = (Event) lstGhostEvent.get(j);
		    if (ghostEvent.getTime() > e1.getTime() && ghostEvent.getTime() < e2.getTime()) {
			result2.add(ghostEvent);
		    } else if (ghostEvent.getTime() >= e2.getTime()) {
			lstIndex = j;
			break;
		    }
		}
	    }
	}
	System.out.println("size result:" + result2.size());
	return result2;
    }

    @Override
    public void pause() {
	flag_start_from_pause = true;
	// callback
	for (OnEngineControlListener listener : _engineControlListener) {
	    listener.onPause();
	}
	switch(STATE_CURRENT){
	    case STATE_PLAY:
		mScheduler.pause();
		STATE_CURRENT=STATE_PAUSE;
		break;
	    case STATE_STOP:
		STATE_CURRENT=STATE_STOP;
		break;
	    case STATE_PAUSE:
		STATE_CURRENT=STATE_PAUSE;
		break;
	}
	System.out.println("current time" + getCurrentTime9E());
    }

    @Override
    public void resume() throws WiException {
	// callback
	for (OnEngineControlListener listener : _engineControlListener) {
	    listener.onResume();
	}
	switch(STATE_CURRENT){
	    case STATE_PAUSE:
		mScheduler.resume();
		STATE_CURRENT=STATE_PLAY;
		break;
	    case STATE_PLAY:
		STATE_CURRENT=STATE_PLAY;
		break;
	    case STATE_STOP:
		STATE_CURRENT=STATE_STOP;
		break;
	}

    }

    @Override
    public void stop() throws WiException {
	flag_start_from_pause = false;

	STATE_CURRENT = STATE_STOP;
	START_TIME = -1;
	FINISH_TIME = -1;
	setCurrentTime9E(0);
	for (OnEngineControlListener listener : _engineControlListener) {
	    listener.onUpdateCurrentTime(getStartTime9E(), getFinishTime9E(), getCurrentTime9E());
	}


	// callback
	for (OnEngineControlListener listener : _engineControlListener) {
	    listener.onStop();
	}



	try {
	    if (future != null) {
		future.cancel(true);
	    }
	    mScheduler.awaitTermination(1, TimeUnit.MILLISECONDS);
	    mScheduler.shutdown();
	    mScheduler.shutdownNow();
//	    this.getAdapter().clear();
//	    this.getAdapter().copyData();
	    mScheduler = new WiSchedulerThreadPoolExecutor();
	} catch (InterruptedException e) {
	    System.out.println(e.toString());
	    throw new WiException(WiException.TYPE_INTERRUPTED, "await interrupt");
	} finally {
	    STATE_CURRENT = STATE_STOP;
	}
    }
    /**
     * @param time : unit: second
     */
    @Override
    public void setStartTime(long time) {
	START_TIME = time;

	// callback
	for (OnEngineControlListener listener : _engineControlListener) {
	    listener.onRunTimeChange(START_TIME, FINISH_TIME);
	}
    }

    public long getStartTime() {
	return START_TIME;
    }

    @Override
    public void setEndTime(long time) {
	FINISH_TIME = time;

	// callback
	for (OnEngineControlListener listener : _engineControlListener) {
	    listener.onRunTimeChange(START_TIME, FINISH_TIME);
	}
    }

    public long getEndTime() {
	return FINISH_TIME;
    }

    @Deprecated
    @Override
    public void setSpeed(double speed) {
	setSpeed(speed, CURRENT_TIME_UNIT);
    }

    public void setSpeed(double speed, TimeUnit timeUnit) {
	CURRENT_SPEED = speed;
	CURRENT_TIME_UNIT = timeUnit;

	// callback
	for (OnEngineControlListener listener : _engineControlListener) {
	    listener.onSpeedChange(speed, timeUnit);
	}
	System.out.println("speed change: " + STATE_CURRENT);
	
	if (isParsed() && STATE_CURRENT==STATE_PLAY) {
	    pause();

	    if (mScheduler != null) {
		mScheduler.purge();
	    }
	    run((long) (getCurrentTime9E() / 1E9), FINISH_TIME);
	}
    }

    public TimeUnit getTimeUnit() {
	return CURRENT_TIME_UNIT;
    }

    protected void setTimeUnit(TimeUnit timeUnit) {
	CURRENT_TIME_UNIT = timeUnit;
    }

    @Override
    public ArrayList<Node> getListNodes(Integer... idNodes) {
	ArrayList<Node> result = new ArrayList<>();
	boolean allNode = false;
	if (idNodes == null || idNodes.length == 0 || idNodes[0] == null || idNodes[0] == -1) {
	    allNode = true;
	}
	for (WiObject object : mAdapter.getListNodes()) {
	    if (!allNode) {
		for (int idNode : idNodes) {
		    if (((Node) object).id == idNode) {
			result.add((Node) object);
		    }
		}
	    } else {
		result.add((Node) object);
	    }

	}
	return result;
    }

    public ArrayList<WiObject> getListNodes(ArrayList<Integer> lstIdNodes) {
	ArrayList<WiObject> result = new ArrayList<>();

	for (WiObject object : mAdapter.getListNodes()) {
	    for (int idNode : lstIdNodes) {
		if (((Node) object).id == idNode) {
		    result.add((Node) object);
		}
	    }
	}

	return result;
    }

    @Override
    public ArrayList<Packet> getListPackets(Integer... idPackets) {
	ArrayList<Packet> result = new ArrayList<>();
	for (WiObject object : mAdapter.getListPackets()) {
	    for (int idPacket : idPackets) {
		if (((Node) object).id == idPacket) {
		    result.add((Packet) object);
		}
	    }
	}
	return result;
    }

    @Override
    public WiNetwork printCurrentNetwork(long time) {

	return null;
    }

    @Override
    public ArrayList<Event> getNodeEvent(int idNode) {
	ArrayList<Event> result = new ArrayList<>();
	for (WiObject object : mAdapter.getListEvents()) {
	    Event event = (Event) object;
	    if (event.destId.equals(idNode + "") || event.nodeId.equals(idNode + "") || event.sourceId.equals(idNode + "")) {
		result.add(event);
	    }
	}
	return result;
    }

    @Override
    public double getSpeed() {
	return CURRENT_SPEED;
    }

    public void setNative(boolean useNative) {
	this.useNative = useNative;

    }

    public void setDebugNative(boolean debugNative) {
	this.debugNative = debugNative;
    }

    /**
     * parse trace file after setTraceFile() method. 2 type parse: use native
     * and use java language. if use native: set var useNative=true; lib:
     * CHelloWorld.dll in same dir project. after parse, analysis() method will
     * called. 1. we create time receive for Events send/forward 2. create ghost
     * event to smooth simulate. 3. create WiNetwork for cache object of
     * network. 4. create list broastCast events
     */
    @Override
    public void parse() throws WiException {
	synchronized (this) {
	    // callback
	    for (OnEngineControlListener listener : _engineControlListener) {
		listener.onBeforeParse();
	    }
	    long crtTime = System.currentTimeMillis();
	    // clear old data
	    mAdapter.clear();
	    if (useNative) {
		System.out.println("use native");
	    } else {
		System.out.println("use java");
	    }
	    mAdapter.parse(false, useNative);
	    // clone data to backup
	    mAdapter.copyData();
	    // create winetwork
	    boolean sync = true;
	    if (sync) {
		// callback
		for (OnEngineControlListener listener : _engineControlListener) {
		    listener.onNotification("starting analysis...");
		}
		long start = System.currentTimeMillis();
		analysis();
		long end = System.currentTimeMillis();
		for (OnEngineControlListener listener : _engineControlListener) {
		    listener.onNotification("finish analysis" + (end - start));
		}
	    } else {
		new Thread() {
		    @Override
		    public void run() {
			for (OnEngineControlListener listener : _engineControlListener) {
			    listener.onNotification("starting analysis...");
			}
			long start = System.currentTimeMillis();
			analysis();
			long end = System.currentTimeMillis();
			for (OnEngineControlListener listener : _engineControlListener) {
			    listener.onNotification("finish analysis" + (end - start));
			}
		    }
		}.start();
	    }
	    STATE_CURRENT=STATE_STOP;
	    // callback
	    for (OnEngineControlListener listener : _engineControlListener) {
		listener.onAfterParse(System.currentTimeMillis() - crtTime);
	    }
	}
    }

    @Override
    public boolean isParsed() {
	return this.mAdapter.isParsed();
    }

    @Override
    public boolean isPlay() {
	return STATE_CURRENT==STATE_PLAY;
    }

    @Override
    public boolean isStop() {
	return STATE_CURRENT==STATE_STOP;
    }
    ArrayList<Integer> sendIdEvent = new ArrayList<>();
    ArrayList<Integer> forwardIdEvent = new ArrayList<>();
    public HashMap<String, ArrayList<WiObject>> hashNode = new HashMap<>();
    public HashMap<String, ArrayList<WiObject>> hashPacket = new HashMap<>();
    public ArrayList<WiObject> lstBroastCastSend = new ArrayList<>();

    private void analysis() {
	if (DEBUG) {
	    System.out.println("start analysis");
	}

	sendIdEvent = new ArrayList<>();
	forwardIdEvent = new ArrayList<>();
//	Issue: - add TimeReceive to send normal Packets.
//	       - delete Receive Event of Normal Packets.

	for (WiObject object : mAdapter.getListEvents()) {
	    Event event = (Event) object;
	    long time = event.getTime();
	    WiNetwork network;
	    if (mNetworks.isEmpty()) {
		network = new WiNetwork(0);
		mNetworks.add(network);
	    } else {
		network = mNetworks.get(mNetworks.size() - 1);
		if (network.get_time() < time) {
		    network = new WiNetwork(network.get_time() + (long) 1E10);
		    mNetworks.add(network);
		}
	    }
	    network = mNetworks.get(mNetworks.size() - 1);


	    String type = event.type;
	    switch (type) {
		case "send":
		    if (!event.destId.equals("-1")) {
			if (DEBUG) {
			    System.out.println("add event send to sendIdEvent");
			}
			sendIdEvent.add(mAdapter.getListEvents().indexOf(event));
			if (DEBUG) {
			    System.out.println("add to sendIdEvent send time:" + event.time);
			}
			// source ID
			ArrayList<WiObject> array = hashNode.get(event.sourceId);
			if (array == null) {
			    array = new ArrayList<>();
			    hashNode.put(event.sourceId, array);
			}
			array.add(event);

			// dest ID
			array = hashNode.get(event.destId);
			if (array == null) {
			    array = new ArrayList<>();
			    hashNode.put(event.destId, array);
			}
			array.add(event);

			// packet ID
			array = hashPacket.get(event.packetId);
			if (array == null) {
			    array = new ArrayList<>();
			    hashPacket.put(event.packetId, array);
			}
			array.add(event);
		    } else {
			// Broastcast
			if (DEBUG) {
			    System.out.println("add send broast cast to lstBroastCastSend");
			}
			lstBroastCastSend.add(object);
		    }

		    break;
		case "receive":
		    // is normal packet:

		    if (!isBroastCastEvent((Event) event)) {
			if (DEBUG) {
			    System.out.println("event receive not broastcast");
			}
			boolean isFound = false;
			for (Integer indexForward : forwardIdEvent) {
			    if (((Event) (mAdapter.getListEvents().get(indexForward))).destId.equals(event.destId)) {
				((Event) mAdapter.getListEvents().get(indexForward)).timeReceive = event.time;
				((Event) mAdapter.getListEvents().get(indexForward)).lstEventReceive.add((Event) object);
				if (DEBUG) {
				    System.out.println("add event forward:" + ((Event) mAdapter.getListEvents().get(indexForward)).timeReceive);
				}
				forwardIdEvent.remove(indexForward);
				isFound = true;
				break;
			    }
			}

			if (!isFound) {
			    if (sendIdEvent.isEmpty() && DEBUG) {
				System.out.println("sendIdEvent size =0");
			    }
			    for (Integer indexSend : sendIdEvent) {
				if (((Event) (mAdapter.getListEvents().get(indexSend))).destId.equals(event.destId)) {
				    ((Event) mAdapter.getListEvents().get(indexSend)).timeReceive = event.time;
				    ((Event) mAdapter.getListEvents().get(indexSend)).lstEventReceive.add((Event) object);
				    if (DEBUG) {
					System.out.println("add event send:" + ((Event) mAdapter.getListEvents().get(indexSend)).timeReceive);
				    }
				    sendIdEvent.remove(indexSend);
				    break;
				}
			    }
			}
		    } else {
			// Broastcast Receive
			if (DEBUG) {
			    System.out.println("receive broast cast");
			}
			for (WiObject objectBrc : lstBroastCastSend) {
			    if (((Event) objectBrc).packetId.equals(((Event) object).packetId)) {
				((Event) objectBrc).lstEventReceive.add((Event) object);
			    }
			}
		    }
		    break;
		case "forward":

		    if (!isBroastCastEvent((Event) event)) {
			if (DEBUG) {
			    System.out.println("add forward to forwardIdEvent");
			}

			boolean isFound = false;
			for (Integer indexForward : forwardIdEvent) {
			    if (((Event) (mAdapter.getListEvents().get(indexForward))).destId.equals(event.sourceId)) {
				((Event) mAdapter.getListEvents().get(indexForward)).timeReceive = event.time;
				((Event) mAdapter.getListEvents().get(indexForward)).lstEventReceive.add((Event) object);
				if (DEBUG) {
				    System.out.println("event forward:" + ((Event) mAdapter.getListEvents().get(indexForward)).timeReceive);
				}
				forwardIdEvent.remove(indexForward);
				isFound = true;
				break;
			    }
			}
			forwardIdEvent.add(mAdapter.getListEvents().indexOf(event));
			if (!isFound) {

			    for (Integer indexSend : sendIdEvent) {

				if (((Event) (mAdapter.getListEvents().get(indexSend))).destId.equals(event.sourceId)) {
				    ((Event) mAdapter.getListEvents().get(indexSend)).timeReceive = event.time;
				    ((Event) mAdapter.getListEvents().get(indexSend)).lstEventReceive.add((Event) object);
				    if (DEBUG) {
					System.out.println("event send:" + ((Event) mAdapter.getListEvents().get(indexSend)).timeReceive);
				    }
				    sendIdEvent.remove(indexSend);
				    break;
				}
			    }
			}


		    }
		    if (DEBUG) {
			System.out.println("forward size:" + event.time);
		    }

		    // source ID
		    ArrayList<WiObject> array = hashNode.get(event.sourceId);
		    if (array == null) {
			array = new ArrayList<>();
			hashNode.put(event.sourceId, array);
		    }
		    array.add(event);

		    // dest ID
		    array = hashNode.get(event.destId);
		    if (array == null) {
			array = new ArrayList<>();
			hashNode.put(event.destId, array);
		    }
		    array.add(event);

		    // packet ID
		    array = hashPacket.get(event.packetId);
		    if (array == null) {
			array = new ArrayList<>();
			hashPacket.put(event.packetId, array);
		    }
		    array.add(event);
		    break;
		case "broastcast":

		    break;
		case "move":
		    // source ID
		    array = hashNode.get(event.nodeId);
		    if (array == null) {
			array = new ArrayList<>();
			hashNode.put(event.nodeId, array);
		    }
		    array.add(event);
		case "sleep":
		    // source ID
		    array = hashNode.get(event.nodeId);
		    if (array == null) {
			array = new ArrayList<>();
			hashNode.put(event.nodeId, array);
		    }
		    array.add(event);
		case "wake":
		    network.getLstEvents().add(event);
		    //System.out.println(event);

		    // source ID
		    array = hashNode.get(event.nodeId);
		    if (array == null) {
			array = new ArrayList<>();
			hashNode.put(event.nodeId, array);
		    }
		    array.add(event);
		    break;

		default:
		    break;
	    }
	}
	System.out.println("Add ghost event");
	long t1 = 0;
	long ghostStep = 9000000;	// minisecond
	for (int i = 0; i < getAdapter().getListEvents().size(); ++i) {
	    Event e = (Event) getAdapter().getListEvents().get(i);
	    long t2 = e.getTime();
	    if ((t2 - t1) > ghostStep) {
		for (long j = t1 + ghostStep; j < t2; j += ghostStep) {
		    Event ghostEvent = new Event();
		    ghostEvent.time = (double) (j / 1E9) + "";
		    ghostEvent.type = "ghost";
		    ghostEvent.timeReceive = (double) ((j + 1) / 1E9) + "";
		    lstGhostEvent.add(ghostEvent);
		    t1 = t2;
		}
	    }
	}
	System.out.println("size ghost: " + lstGhostEvent.size());
	System.out.println("-finsish analysis: ");
    }
    ArrayList<WiObject> lstGhostEvent = new ArrayList<>();

    public ArrayList<WiObject> getNodeInfo(int idNode) {
	return hashNode.get(idNode + "");
    }

    public ArrayList<WiObject> getPacketInfo(int idPacket) {
	return hashPacket.get(idPacket + "");
    }

    @Override
    public void removeDrawerListener() {
	this.mLstDrawListener.removeAll(this.mLstDrawListener);
    }

    public boolean isBroastCastPacket(String id) {
	for (WiObject object : mAdapter.getListPackets()) {
	    Packet packet = (Packet) object;
	    if (packet.id.equals(id)) {
		if (packet.destId.equals("-1") || packet.sourceId.equals("-1")) {
		    return true;
		} else {
		    return false;
		}
	    }
	}
	return false;
    }

    public boolean isBroastCastEvent(Event event) {
	if (event.destId.equals("-1") || event.sourceId.equals("-1")) {
	    return true;
	}
	return false;
    }
    // call back for engine control listener 
    public ArrayList<OnEngineControlListener> _engineControlListener = new ArrayList<>();

    public void addEngineControlListener(OnEngineControlListener listener) {
	if (_engineControlListener.contains(listener)) {
	    return;
	}
	_engineControlListener.add(listener);
    }

    public void removeEngineControlListener(OnEngineControlListener listener) {
	_engineControlListener.remove(listener);
    }

    /**
     * controller for engine. to update UI of console/graph. handle callback
     */
    public interface OnEngineControlListener {

	/**
	 * not use
	 */
	public void onNotification(Object... text);

	/**
	 * when setTracefile is called
	 */
	public void onTraceFileChange(String traceFile);

	/**
	 * start simulation from startTimeSecond to endTimeSecond
	 */
	public void onRun(long startTimeSecond, long endTimeSecond);

	/**
	 * pause callback for simulation.
	 */
	public void onPause();

	/**
	 * resume callback for simulation.
	 */
	public void onResume();

	/**
	 * stop simulation callback
	 */
	public void onStop();
	/**
	 * change speed callback
	 */
	public void onSpeedChange(double speed, TimeUnit timeunit);
	/**
	 * before parse callback.
	 */
	public void onBeforeParse();
	/**
	 * after parse callback. You can not get array list nodes/packets here.
	 */
	public void onAfterParse(long timeParse);
	/**
	 * when user set difference time start and finish
	 */
	public void onRunTimeChange(long startTimeSecond, long finishTimeSecond);
	/**
	 * call when is ghost event. update smooth time (seekBar) here.
	 */
	public void onUpdateCurrentTime(long startTime9E, long endTime9E, long currentTime9E);
    }
}
